<?php
require_once(SITE_PATH . 'class/Location.php');
require_once(SITE_PATH . 'class/Progress.php');

class Predictor {
    private $db;
    const TRAINING_SET_SIZE = 80;
    const MAX_DB_ROWS = 20000;
    const NEIGHBORS = 1;

    private $fields = array(
        'kirjeldus_yldpind' => 1,
        'kirjeldus_toad' => 1,
        'asukoht_korruseid' => 1,
        'asukoht_korrus' => 1,
/*
        'seisukord_turvauks' => 1,
        'seisukord_lift' => 1,
        'kirjeldus_keskkyte' => 1,
        'kirjeldus_elkyte' => 1,
        'kirjeldus_gaasikyte' => 1,
        'kirjeldus_ahjukyte' => 1,
        'kirjeldus_kelder' => 1,
        'kirjeldus_garaazh' => 1,
        'kirjeldus_parkett' => 1,
        'kirjeldus_kamin' => 1,
        'kirjeldus_dush' => 1,
        'kirjeldus_vann' => 1,
        'kirjeldus_saun' => 1,
        'kirjeldus_boiler' => 1,
        'kirjeldus_rodu' => 1,
        'kirjeldus_moobel' => 1,
        'lat' => 1,
        'lon' => 1,
*/
        'price_per_m2' => 1
    );

    private $fieldMin = array();
    private $fieldMax = array();
    private $fieldSpan = array();

    private $fieldsUpper = array();

    public function __construct($db) {
        $this->db = $db;
    }

    private function debug($message) {
        if (is_array($message)) {
            print_r($message);
        } else {
            echo $message;
        }
        echo "\n";
    }

    public function predict() {
        $objects = $this->getAllREObjects();
        shuffle($objects);
        $trainList = $this->getTrainingObjects($objects, self::TRAINING_SET_SIZE);
        $this->assignFieldExtremums($trainList);
        $testList = $this->getTestObjects($objects, self::TRAINING_SET_SIZE);
        unset($objects);

        $priceDiff = array();

        $this->debug(date('H:i:s'));
        $progress = new Progress(sizeof($testList));
        foreach ($testList as $testKey => $testObject) {
            $progress->show();
            $distList = array();
            foreach ($trainList as $trainId => $trainObject) {
                $dist = $this->getDistance($testObject, $trainObject);
                $distList[$trainId] = $dist;
            }
//            $testList[$testKey]['dist'] = $dist;
            $testList[$testKey]['price_per_m2_estimate'] = $this->getNearestPrice($distList, $trainList);

            //print_r($testList[$testKey]);
            $priceDiff[] = $this->getForecastError($testList[$testKey]['price_per_m2'], $testList[$testKey]['price_per_m2_estimate']);
        }

        sort($priceDiff);
        $this->debug('Avg: ' . $this->getArrayAverage($priceDiff));
        $this->debug($priceDiff);
        $this->debug(date('H:i:s'));
    }

	private function getNearestPrice($distList, $trainList) {
        asort($distList);
        $count = 1;
        $sum = 0;
        foreach ($distList as $id => $dist) {
            $count++;
            $sum += $trainList[$id]['price_per_m2'];
            if ($count > self::NEIGHBORS) {
                break;
            }
        }
        return $sum / self::NEIGHBORS;
    }

    private function getArrayAverage($array) {
        $sum = 0;
        foreach ($array as $val) {
            $sum += $val;
        }
        return $sum / sizeof($array);
    }

    private function getForecastError($priceReal, $priceEstimate) {
        $diff = abs($priceReal - $priceEstimate) / $priceReal;
        return $diff;
    }

    private function assignFieldExtremums($objects) {
        foreach ($objects as $object) {
            foreach ($object as $field => $value) {
                if (!isset($this->fieldMin[$field]) || $this->fieldMin[$field] > $value) {
                    $this->fieldMin[$field] = $value;
                }
                if (!isset($this->fieldMax[$field]) || $this->fieldMax[$field] < $value) {
                    $this->fieldMax[$field] = $value;
                }
            }
        }

        foreach ($this->fieldMin as $field => $value) {
            $this->fieldSpan[$field] = $this->fieldMax[$field] - $value;
        }
        $this->debug($this->fieldSpan);
    }

    private function getAllREObjects() {
        $objects = array();
        $start = rand(0, 75000 - self::MAX_DB_ROWS);
        $this->debug($start);
        $sql = 'SELECT * FROM city2 LIMIT ' . $start . ', ' . self::MAX_DB_ROWS;
        foreach ($this->db->query($sql) as $data) {
            $objects[] = $this->getREObjectFromData($data);
        }
        return $objects;
    }

    private function getREObjectFromData($data) {
        $obj = array();
        foreach ($this->fields as $field => $weight) {
            if ($field == 'lat' || $field == 'lon') {
                continue;
            }
            $obj[$field] = $data[strtoupper($field)];
        }
        if (array_key_exists('lat', $this->fields)) {
            list($obj['lat'], $obj['lon']) = explode(',', $data['coord']);
        }
        return $obj;
    }

    private function getTrainingObjects($objects, $percent) {
        $beg = 0;
        $len = round(sizeof($objects) * $percent / 100);
        return array_slice($objects, $beg, $len);
    }

    private function getTestObjects($objects, $percent) {
        $beg = round(sizeof($objects) * $percent / 100);
        $len = sizeof($objects) - $beg;
        return array_slice($objects, $beg, $len);
    }

    private function getDistance($src, $tar) {
        $distance = 0;
        foreach ($this->fields as $field => $weight) {
            if ($field == 'price_per_m2') {
                continue;
            }
            if ($this->fieldSpan[$field] == 0) {
                $diff = 0;
            } else {
                $diff = ($src[$field] - $tar[$field]) / $this->fieldSpan[$field];
            }
            $distance += ($diff * $diff) * $weight;
        }
        return sqrt($distance);
    }
}
